package cn.bonoon.kernel.io;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import javax.servlet.ServletContext;

import org.apache.commons.codec.binary.Base64;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.context.ServletContextAware;
import org.springframework.web.multipart.MultipartFile;

import cn.bonoon.kernel.io.devices.LocalStorageDevice;
import cn.bonoon.kernel.io.devices.RemoteStorageDevice;
import cn.bonoon.kernel.io.devices.RemoteStorageFile;
import cn.bonoon.kernel.io.devices.TemporaryStorageCached;
import cn.bonoon.kernel.io.redirect.FileRedirectHandler;
import cn.bonoon.kernel.util.StringHelper;
import cn.bonoon.util.PathUtil;

public class StandardFileManager extends AbstractFileManager implements ServletContextAware, InitializingBean, FileMonitor{
	
	/** 默认值：{@value #DEFAULT_ROOT_DIRECTORY} */
	private final static String DEFAULT_ROOT_DIRECTORY = PathUtil.separatorChar + "upload";
	/** 默认值：{@value #DEFAULT_TEMPORARY_DIRECTORY} */
	private final static String DEFAULT_TEMPORARY_DIRECTORY = DEFAULT_ROOT_DIRECTORY + PathUtil.separatorChar + "temporary";
	
	private ServletContext servletContext;
	
	/** 文件存储与读取的流的定义，可以把文件分流到指定的目录或服务器上；参考：{@link FSResource} */
	private RemoteStorageDevice[] remoteDevices;
	
	/** 
	 * 参考{@link org.springframework.orm.jpa.persistenceunit.DefaultPersistenceUnitManager#buildDefaultPersistenceUnitInfo buildDefaultPersistenceUnitInfo} 
	 * 需要被扫描的路径，这个路径里是各个用于存储文件的数据库表的定义，这些
	 * */
	private String scanPackage;
	
	private ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();

	/** 文件的实时同步任务处理器 */
	private FileRuntimeSynchronizeTask synchronizeTask;

	/** 请求/需要释放空间 */
	private int demandReleaseSpace;

	private List<LocalStorageDevice> localDevices = new ArrayList<>();

	private LocalStorageDevice root;
	
	private TemporaryStorageCached temporary;
	
	/**
	 * pic2:/2017/08/dddd
	 * 文件的实时同步任务处理对象
	 * @author jackson
	 *
	 */
	private class FileRuntimeSynchronizeTask extends AbstractTaskThread<RemoteStorageFile>{

		private final RemoteStorageDevice[] runtimeSynchronizes;
		
		protected FileRuntimeSynchronizeTask(RemoteStorageDevice[] runtimeSynchronizes) {
			super(false, true, 2);
			
			//使用两个线程是足够的了
			setPriority(1);
			setBatchTaskSize(50);
			
			this.runtimeSynchronizes = runtimeSynchronizes;
		}

		@Override
		protected boolean executeTask(RemoteStorageFile task) throws Exception {
			/*
			 *  TODO 把本地文件copy到存储设备或远程服务器上
			 *  这里需要使用更低的优先级的线程进行处理；
			 *  或者里查看网络情况，如果网络占用量大，则这个线程不应该被启动处理
			 *  
			 *  在这里实时的把文件同步到指定的设备或服务器了，根据配置的要求，
			 *  是否可以删除本地的文件，释放更多的本地空间；如果是，则只做删除
			 *  的标示，不会实时删除，在系统没有足够的空间时，空间检查的任务才会
			 *  被启动，用于动态的检查可释放的空间，并根据时间顺序来删除本地文件；
			 */
			for(RemoteStorageDevice device : runtimeSynchronizes){
				
				//要在这里休息一下，不要那么急着操作
				Thread.sleep(100);
				
				device.store(task);
			}
			return true;
		}
		
	}
	
	@Override
	public void runTask() {
		// TODO Auto-generated method stub
		
	}
	
	/** 指定主目录最小的空间，如果硬盘空间小于这个指定的大小，则不再添加文件 */
	private long rootMinLength = 200 * FileSize.G;
	
	private long temporaryMinLength;
	
	@Override
	public void afterPropertiesSet() throws Exception {

		this.fileLoadUrl = PathUtil.path(servletContext.getContextPath(), FILE_LOAD_URL);
		this.fileOpenUrl = PathUtil.path(servletContext.getContextPath(), FILE_OPEN_URL);
		
		// TODO 必要保存两个文件夹的存在
		root = new LocalStorageDevice(DEFAULT_ROOT_DIRECTORY, rootMinLength);//  PathUtil.generateFolder(servletContext.getRealPath(rootDirectory));
		temporary = new TemporaryStorageCached(DEFAULT_TEMPORARY_DIRECTORY, temporaryMinLength); //PathUtil.generateFolder(servletContext.getRealPath(temporaryDirectory));
		
		temporary.init(servletContext);
		
		localDevices.add(0, root);
		for(LocalStorageDevice device : localDevices){
			device.init(servletContext);
		}
		localDevices = Collections.unmodifiableList(localDevices);
		
		if(null == remoteDevices){
			remoteDevices = new RemoteStorageDevice[0];
		}else{
			/*
			 * 这里有定义的存储设备或服务器的情况
			 * 1. 对存储设备或服务器的优化级进行排序
			 * 2. 对存储设备或服务器进行初始化
			 * 3. 启动工作任务的线程
			 */
			Arrays.sort(remoteDevices);
			
			List<RemoteStorageDevice> rss = new ArrayList<>();
			for(RemoteStorageDevice device : remoteDevices){
				device.initDevice(this, temporary);
				
				if(device.isRuntimeSynchronize()){
					rss.add(device);
				}
			}
			
			if(!rss.isEmpty()){
				/*
				 * 如果没有定义实时的文件传输的，则实时文件传输处理器将不会被启动
				 * 这里不需要马上启动处理线程，因为还没有接收到上传文件
				 */
				synchronizeTask = new FileRuntimeSynchronizeTask(rss.toArray(new RemoteStorageDevice[rss.size()]));
			}
		}
		
		if(null != scanPackage){
			List<Class<?>> candidates = new ArrayList<>();
			
			/*
			 * 扫描定义的包，把这些包里的所有定义有StoragePersistable接口的类读取出来
			 */
			String[] basePackages = StringUtils.tokenizeToStringArray(scanPackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
			for (String basePackage : basePackages) {
				String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + 
						ClassUtils.convertClassNameToResourcePath(basePackage) + "/**/*.class";
				Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath);
				MetadataReaderFactory readerFactory = new CachingMetadataReaderFactory(this.resourcePatternResolver);
				for (Resource resource : resources) {
					if (resource.isReadable()) {
						MetadataReader reader = readerFactory.getMetadataReader(resource);
						String className = reader.getClassMetadata().getClassName();
						Class<?> cls = Class.forName(className);
						if(cls.isAssignableFrom(StoragePersistable.class)){
							candidates.add(cls);
						}
					}
				}
			}
			
			/*
			 * 对扫描出来的候选的类进行再次处理 
			 */
		}
	}
	
	private String fileLoadUrl, fileOpenUrl;

	private String resolveLoadingUrl(String url, String par) throws Exception{
		return url + "?" + FILE_REQUEST_PARAMETER + "=" + encodePath(par);
	}
	//  所有调用到下面两个方法的VM都得修改
	@Override
	public String loadPath(String path) throws Exception {
		return resolveLoadingUrl(fileLoadUrl, path);
	}
	
	@Override
	public String openPath(String path) throws Exception {
		return resolveLoadingUrl(fileOpenUrl, path);
	}
	
	@Override
	public String loadPath(StoragePersistable path) throws Exception {
		return resolveLoadingUrl(fileLoadUrl, path.getPath());
	}
	
	@Override
	public String openPath(StoragePersistable path) throws Exception {
		return resolveLoadingUrl(fileOpenUrl, path.getPath());
	}
	
	protected String encodePath(String path) throws Exception{
		if(StringHelper.isEmpty(path)) return "";
		return Base64.encodeBase64String(path.getBytes(charsetCode));
	}
	
	private String charsetCode = "UTF-8";
	
	@Override
	public String initPath(String path) {
		return path;
	}
	
	/** 内部的文件加载 */
	@Override
	protected boolean internalFileLoading(HttpStorageFile sfile) throws Exception{
		OutputStream oStream = sfile.getOutputStream();
		for(LocalStorageDevice device : localDevices){
			//这个路径有可能是一个文件，也可能是一个文件夹，如果是文件夹，则需要使用文件名和后缀组成完整的路径来访问文件
			File file = device.open(sfile);//resolveRealPath(sfile);
			
			if(file.isDirectory()){
				//如果查找到的是目录，则需要在这个目录下搜索需要的文件
				file = new File(file, sfile.getFilename());
			}
			
			if(file.isFile()){
			
				try(InputStream iStream = new FileInputStream(file)){
					
					byte[] b = new byte[1024];
					int length;
					while ((length = iStream.read(b)) != -1) {
						oStream.write(b, 0, length);
					}
				}
				
				return true;
			}
		}
		
		if(null != remoteDevices){
			/*
			 * 如果本地的文件已经被删除了或者没有进行本地的存储，则从
			 * 定义的存储设备(服务器)上，找到了需要的文件；
			 * 优化使用本地，因为使用其它地方的，可以会使用到网络或需要读取移动硬盘等
			 */
			for(RemoteStorageDevice device : remoteDevices){
				if(device.load(sfile, oStream)){
					return true;
				}
			}
		}
		/*
		 * 这里是在所有已经定义的存储设备(服务器)里找不到该文件的情况
		 * 如果找不到，则应该由外部调用的类来决定如何处理
		 */
		return false;
	}

	@Override
	protected FileInfo[] internalFileStored(StorageFile file, int limit) throws Exception {
		
		/*
		 * 1. 先在本地存储
		 * 2. 根据需要，是否存储到指定的设备（或服务器）
		 * 3. 存储到指定设备（或服务器）成功后，根据指定来决定是否需要删除本地的文件
		 */
		
		//先预处理一下
		List<MultipartFile> mFiles = new ArrayList<>();
		long needSpaceLength = 0;//需要存储的空间
		for(MultipartFile mf : file.getSourceFile()){
			if(mf.isEmpty()) continue;
			needSpaceLength += mf.getSize();
			mFiles.add(mf);
		}
		
		LocalStorageDevice localStorage = null;
		for(LocalStorageDevice device : localDevices){
			if(device.checkFreeSpace(needSpaceLength)){
				//可用
				localStorage = device;
				break;
			}
		}
		
		Assert.notNull(localStorage, "没有找到存储的设备或存储空间不足！");
		
		File folder = localStorage.open(file);
		if(!folder.isDirectory()) folder.mkdirs();
		
		//处理的结果，表示已经处理了多少个上传文件
		FileStoreHandler storeHandler = findStoreHandler(file);
		List<FileInfo> fis = new ArrayList<>();
		
		for(MultipartFile mf : mFiles){
			
			FileInfo fileInfo = null;
			for(int i = 0; ; i++){
				/* 具体需要被存储的文件 */
				File targetfile = new File(folder, file.resolveFilename(mf, i));
				if(!targetfile.exists()) {
					fileInfo = new FileInfo(mf, targetfile, file);
					break;
				}
			}
			
			fileInfo = storeHandler.stored(fileInfo);
			
			if(null != fileInfo){
				//处理器"storeHandler"可能返回null值，表示这个文件存储不成功
				fis.add(fileInfo);
				if(limit > 0 && fis.size() >= limit) break;
			}
		}
		FileInfo[] fileStoredResult =  fis.toArray(new FileInfo[fis.size()]);
		
		/*
		 * 需要把文件存储到其它设备或服务器上的，都通过另外的线程进行处理，这样就不会占用主线程
		 * 并且在其它设备或服务器存储成功后，需要把本地的缓存文件删的，也只是把标志添加在数据库表
		 * 什么时候删除的，则应该由单独的删除线程去判断处理
		 */
		if(null != synchronizeTask){
			RemoteStorageFile[] rfs = new RemoteStorageFile[fileStoredResult.length];
			for(int i = 0; i < fileStoredResult.length; i++){
				rfs[i] = new RemoteStorageFile(file, fileStoredResult[i]);
			}
			
			synchronizeTask.add(rfs);
			
			synchronizeTask.start();
		}
		
		return fileStoredResult;
	}
	
	protected FileStoreHandler storageHandler = new FileStoreHandler(){

		@Override
		public FileInfo stored(FileInfo fileInfo) throws Exception {
			fileInfo.sourceFile.transferTo(fileInfo.targetFile);
			return fileInfo;
		}
		
	};
	
	protected FileStoreHandler findStoreHandler(StorageFile file){
		FileStoreHandler storageHandler = file.getStorageHandler();
		if(null == storageHandler) storageHandler = this.storageHandler;
		return storageHandler;
	}
	
	public String getScanPackage() {
		return scanPackage;
	}

	public void setScanPackage(String scanPackage) {
		this.scanPackage = scanPackage;
	}

	@Override
	public void setServletContext(ServletContext sc) {
		this.servletContext = sc;
	}
	
	public void setRemoteDevices(RemoteStorageDevice[] remoteDevices) {
		this.remoteDevices = remoteDevices;
	}
	
	public void setLocalDevices(LocalStorageDevice[] localDevices) {
		Collections.addAll(this.localDevices, localDevices);
	}
	
	public void setTemporaryMinLength(long temporaryMinLength) {
		this.temporaryMinLength = temporaryMinLength;
	}
	
	public void setRootMinLength(long rootMinLength) {
		this.rootMinLength = rootMinLength;
	}
	
	//以下尝试跳转到远程服务器
	private FileRedirectHandler redirectHandler;
	
	@Override
	public void redirectOpen(HttpStorageFile file) throws Exception{
		if(null != redirectHandler){
			redirectHandler.redirectOpen(file);
		}
	}

	@Override
	public void redirectLoad(HttpStorageFile file) throws Exception{
		if(null != redirectHandler){
			redirectHandler.redirectLoad(file);
		}
	}
	
	/** 如果有定义，则通过配置文件注入 */
	public void setRedirectHandler(FileRedirectHandler redirectHandler) {
		this.redirectHandler = redirectHandler;
	}
}
